/** * mingkos Customization System - Enhanced AJAX Handlers (Merged & Fixed) * 印刷产品定制系统 - 增强版AJAX处理函数(合并修复版) * * @version 5.3.1 - 移除重复代码,优化函数定义 * @package mingkos */ // =============================== // 🛡️ SECURITY CHECK // =============================== if (!defined('ABSPATH')) { exit; } // Prevent duplicate loading if (defined('mingkos_AJAX_ENHANCED_LOADED')) { return; } define('mingkos_AJAX_ENHANCED_LOADED', true); // =============================== // 📝 确保日志函数存在 // =============================== if (!function_exists('mingkos_log')) { function mingkos_log($message, $data = []) { if (defined('WP_DEBUG') && WP_DEBUG) { $log_entry = '[' . current_time('mysql') . '] mingkos: ' . $message; if (!empty($data)) { $log_entry .= ' | Data: ' . json_encode($data, JSON_UNESCAPED_UNICODE); } error_log($log_entry); } } } // =============================== // 🚀 ENHANCED AJAX HANDLERS // =============================== /** * Enhanced AJAX handler for adding customized product to cart * 增强版AJAX处理函数:添加定制产品到购物车 */ if (!function_exists('mingkos_enhanced_add_to_cart_ajax')) { add_action('wp_ajax_mingkos_enhanced_add_to_cart', 'mingkos_enhanced_add_to_cart_ajax'); add_action('wp_ajax_nopriv_mingkos_enhanced_add_to_cart', 'mingkos_enhanced_add_to_cart_ajax'); function mingkos_enhanced_add_to_cart_ajax() { // Start output buffering ob_start(); // Log start of request mingkos_log('=== Enhanced AJAX Add to Cart Start ===', [ 'time' => current_time('mysql'), 'method' => $_SERVER['REQUEST_METHOD'], 'content_type' => $_SERVER['CONTENT_TYPE'] ?? 'N/A' ]); // === 调试1:记录原始 POST 数据 === if (defined('WP_DEBUG') && WP_DEBUG) { error_log('=== [AJAX] Raw POST ==='); error_log(print_r($_POST, true)); error_log('=== End Raw POST ==='); } try { // Check WooCommerce if (!class_exists('WooCommerce')) { throw new Exception('WooCommerce not active'); } // Verify nonce $nonce = sanitize_text_field($_POST['mingkos_nonce'] ?? ($_POST['nonce'] ?? '')); if (!wp_verify_nonce($nonce, 'mingkos_add_to_cart')) { throw new Exception('Security check failed. Please refresh the page and try again.'); } // Get and validate product ID $product_id = intval($_POST['mingkos_product_id'] ?? ($_POST['product_id'] ?? 0)); if ($product_id <= 0) { throw new Exception('Invalid product ID'); } $product = wc_get_product($product_id); if (!$product || !$product->is_purchasable()) { throw new Exception('Product not found or not purchasable'); } // Check if customization is enabled if (function_exists('mingkos_is_customizable') && !mingkos_is_customizable($product_id)) { throw new Exception('Customization is not enabled for this product'); } // Get configuration from POST data $configuration = mingkos_sanitize_ajax_configuration($_POST); if (defined('WP_DEBUG') && WP_DEBUG) { error_log('=== [AJAX] Parsed Configuration ==='); error_log(print_r($configuration, true)); error_log('=== End Parsed Configuration ==='); } // Log configuration mingkos_log('Configuration received', [ 'product_id' => $product_id, 'order_type' => $configuration['order_type'], 'quantity' => $configuration['quantity'], 'attributes_count' => count($configuration['attributes'] ?? []), 'printing_count' => count($configuration['printing_options'] ?? []), 'packaging_count' => count($configuration['packaging_options'] ?? []), 'dynamic_count' => count($configuration['dynamic_parameters'] ?? []), 'calculator_area' => $configuration['calculator_costs']['area'] ?? 0, 'calculator_volume' => $configuration['calculator_costs']['volume'] ?? 0 ]); // Validate configuration $validation = mingkos_validate_enhanced_configuration($product_id, $configuration); if (!$validation['valid']) { throw new Exception(implode(', ', $validation['errors'])); } // Calculate price using enhanced engine or fallback if (function_exists('mingkos_calculate_product_price_complete')) { $price_calculation = mingkos_calculate_product_price_complete($product_id, $configuration); } else if (function_exists('mingkos_calculate_product_price')) { $price_calculation = mingkos_calculate_product_price($product_id, $configuration); } else { // Fallback to basic price calculation $base_price = $product->get_price(); $quantity = $configuration['quantity']; $price_calculation = [ 'success' => true, 'total' => $base_price * $quantity, 'price_per_unit' => $base_price, 'subtotal' => $base_price * $quantity, 'base_price' => $base_price, 'currency' => get_woocommerce_currency() ]; } if (!$price_calculation['success']) { throw new Exception($price_calculation['error'] ?? 'Price calculation failed'); } // Handle file uploads $design_files = []; if (function_exists('mingkos_process_design_files')) { $design_files = mingkos_process_design_files($product_id, $_FILES); } else if (!empty($_FILES['design_files'])) { // Fallback file processing $design_files = mingkos_process_design_files_fallback($_FILES); } // Add configuration to cart item data $cart_item_data = [ 'mingkos_customization' => true, 'mingkos_config' => $configuration, 'mingkos_price_data' => $price_calculation, 'mingkos_hash' => mingkos_generate_config_hash($configuration), 'mingkos_timestamp' => current_time('timestamp'), 'mingkos_version' => '5.3.1' ]; // Add design files if uploaded if (!empty($design_files)) { $cart_item_data['mingkos_design_files'] = $design_files; $configuration['design_files'] = $design_files; } // Generate customization summary $current_lang = function_exists('mingkos_get_user_language') ? mingkos_get_user_language() : 'en_US'; if (function_exists('mingkos_generate_config_summary_updated')) { $summary = mingkos_generate_config_summary_updated($product_id, $configuration, $current_lang); } else if (function_exists('mingkos_generate_config_summary')) { $summary = mingkos_generate_config_summary($product_id, $configuration); } else { $summary = sprintf(__('Customized: %s', 'mingkos'), $product->get_name()); } $cart_item_data['mingkos_summary'] = $summary; // 记录即将存入购物车的数据 if (defined('WP_DEBUG') && WP_DEBUG) { error_log('=== [AJAX] Cart Item Data ==='); error_log(print_r($cart_item_data, true)); error_log('=== End Cart Item Data ==='); } // Add to cart $variation_id = $configuration['variation_id'] ?? 0; $cart_item_key = WC()->cart->add_to_cart( $product_id, $configuration['quantity'], $variation_id, [], // Variation attributes $cart_item_data ); if (!$cart_item_key) { throw new Exception('Failed to add product to cart'); } // Calculate cart totals WC()->cart->calculate_totals(); // Get updated cart fragments $cart_fragments = mingkos_get_enhanced_cart_fragments(); // Generate success response $response = [ 'success' => true, 'message' => __('Product added to cart successfully!', 'mingkos'), 'cart_item_key' => $cart_item_key, 'cart_item_data' => [ 'product_name' => $product->get_name(), 'quantity' => $configuration['quantity'], 'custom_price' => $price_calculation['price_per_unit'], 'total_price' => $price_calculation['total'] ], 'cart_fragments' => $cart_fragments, 'redirect_url' => wc_get_cart_url(), 'price_summary' => [ 'unit_price' => wc_price($price_calculation['price_per_unit']), 'total_price' => wc_price($price_calculation['total']), 'quantity' => $configuration['quantity'], 'breakdown' => $price_calculation['breakdown'] ?? [] ], 'configuration_summary' => $summary, 'customization_hash' => $cart_item_data['mingkos_hash'] ]; // Log success mingkos_log('Add to cart successful', [ 'cart_item_key' => $cart_item_key, 'total_price' => $price_calculation['total'], 'cart_count' => WC()->cart->get_cart_contents_count() ]); wp_send_json_success($response); } catch (Exception $e) { // Log error mingkos_log('Add to cart error', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); wp_send_json_error([ 'success' => false, 'message' => $e->getMessage(), 'code' => 'mingkos_ERROR' ]); } // End output buffering and send response ob_end_clean(); } } /** * Sanitize AJAX configuration data (Enhanced) * 清理AJAX配置数据(增强版) */ if (!function_exists('mingkos_sanitize_ajax_configuration')) { function mingkos_sanitize_ajax_configuration($post_data) { $config = [ 'order_type' => 'normal', 'quantity' => 1, 'attributes' => [], 'printing_options' => [], 'packaging_options' => [], 'packaging_type' => 'standard', 'dynamic_parameters' => [], 'calculator_costs' => ['area' => 0, 'volume' => 0], 'calculator_inputs' => [], 'order_note' => '', 'design_files' => [], 'variation_id' => 0 ]; // 订单类型 if (isset($post_data['order_type'])) { $config['order_type'] = in_array($post_data['order_type'], ['normal', 'sample']) ? sanitize_text_field($post_data['order_type']) : 'normal'; } // 数量 if (isset($post_data['order_quantity'])) { $config['quantity'] = max(1, intval($post_data['order_quantity'])); } elseif (isset($post_data['quantity'])) { $config['quantity'] = max(1, intval($post_data['quantity'])); } // 属性:尝试解析 JSON 或直接使用数组 if (isset($post_data['attributes'])) { $attributes = $post_data['attributes']; if (is_string($attributes)) { $attributes = json_decode(stripslashes($attributes), true); } if (is_array($attributes)) { $config['attributes'] = $attributes; } } // 印刷选项 if (isset($post_data['printing_options'])) { $printing = $post_data['printing_options']; if (is_string($printing)) { $printing = json_decode(stripslashes($printing), true); } if (is_array($printing)) { $config['printing_options'] = $printing; } } // 包装选项 if (isset($post_data['packaging_options'])) { $packaging = $post_data['packaging_options']; if (is_string($packaging)) { $packaging = json_decode(stripslashes($packaging), true); } if (is_array($packaging)) { $config['packaging_options'] = $packaging; } } // 包装类型 if (isset($post_data['packaging_type'])) { $config['packaging_type'] = sanitize_text_field($post_data['packaging_type']); } // 动态参数 if (isset($post_data['dynamic_parameters'])) { $dynamic = $post_data['dynamic_parameters']; if (is_string($dynamic)) { $dynamic = json_decode(stripslashes($dynamic), true); } if (is_array($dynamic)) { $config['dynamic_parameters'] = $dynamic; } } // 计算器成本 if (isset($post_data['calculator_area'])) { $config['calculator_costs']['area'] = max(0, floatval($post_data['calculator_area'])); } if (isset($post_data['calculator_volume'])) { $config['calculator_costs']['volume'] = max(0, floatval($post_data['calculator_volume'])); } // ========== 修复:收集计算器输入值(只定义一次) ========== $config['calculator_inputs'] = mingkos_collect_calculator_inputs($post_data); // 订单备注 if (isset($post_data['order_note'])) { $config['order_note'] = sanitize_textarea_field($post_data['order_note']); } // 变体ID if (isset($post_data['variation_id'])) { $config['variation_id'] = intval($post_data['variation_id']); } return apply_filters('mingkos_sanitized_configuration', $config); } } /** * 收集计算器输入值(独立函数,避免重复代码) */ if (!function_exists('mingkos_collect_calculator_inputs')) { function mingkos_collect_calculator_inputs($post_data) { $inputs = []; // 面积计算器输入 if (isset($post_data['calculator_area_input']) && is_array($post_data['calculator_area_input'])) { $inputs['area'] = [ 'length' => floatval($post_data['calculator_area_input']['length'] ?? 0), 'width' => floatval($post_data['calculator_area_input']['width'] ?? 0), 'length_unit' => sanitize_text_field($post_data['calculator_area_input']['length_unit'] ?? 'm'), 'width_unit' => sanitize_text_field($post_data['calculator_area_input']['width_unit'] ?? 'm'), ]; } elseif (isset($post_data['area_length']) && isset($post_data['area_width'])) { // 兼容旧格式 $inputs['area'] = [ 'length' => floatval($post_data['area_length'] ?? 0), 'width' => floatval($post_data['area_width'] ?? 0), 'length_unit' => sanitize_text_field($post_data['area_length_unit'] ?? 'm'), 'width_unit' => sanitize_text_field($post_data['area_width_unit'] ?? 'm'), ]; } // 体积计算器输入 if (isset($post_data['calculator_volume_input']) && is_array($post_data['calculator_volume_input'])) { $inputs['volume'] = [ 'length' => floatval($post_data['calculator_volume_input']['length'] ?? 0), 'width' => floatval($post_data['calculator_volume_input']['width'] ?? 0), 'height' => floatval($post_data['calculator_volume_input']['height'] ?? 0), 'unit' => sanitize_text_field($post_data['calculator_volume_input']['unit'] ?? 'm'), ]; } elseif (isset($post_data['volume_length']) && isset($post_data['volume_width']) && isset($post_data['volume_height'])) { // 兼容旧格式 $inputs['volume'] = [ 'length' => floatval($post_data['volume_length'] ?? 0), 'width' => floatval($post_data['volume_width'] ?? 0), 'height' => floatval($post_data['volume_height'] ?? 0), 'unit' => sanitize_text_field($post_data['volume_unit'] ?? 'm'), ]; } return $inputs; } } // =============================== // 🛠️ VALIDATION FUNCTIONS // =============================== /** * Validate enhanced configuration * 验证增强版配置 */ if (!function_exists('mingkos_validate_enhanced_configuration')) { function mingkos_validate_enhanced_configuration($product_id, $config) { $errors = []; // Get product data if (function_exists('mingkos_get_product_data_enhanced')) { $product_data = mingkos_get_product_data_enhanced($product_id); } elseif (function_exists('mingkos_get_product_data')) { $product_data = mingkos_get_product_data($product_id); } else { $product_data = ['enabled' => true]; } if (!$product_data || !$product_data['enabled']) { $errors[] = 'Product customization is not enabled'; return ['valid' => false, 'errors' => $errors]; } // Validate order type $allowed_order_types = ['normal']; if (isset($product_data['sample_enabled']) && $product_data['sample_enabled']) { $allowed_order_types[] = 'sample'; } if (!in_array($config['order_type'], $allowed_order_types)) { $errors[] = 'Invalid order type'; } // Validate quantity based on order type $quantity = intval($config['quantity']); if ($config['order_type'] === 'sample') { // Sample order validation if (!isset($product_data['sample_enabled']) || !$product_data['sample_enabled']) { $errors[] = 'Sample ordering is not enabled for this product'; } if ($quantity < 1) { $errors[] = 'Sample order minimum quantity is 1'; } $sample_max_qty = intval($product_data['sample_max_qty'] ?? 5); if ($quantity > $sample_max_qty) { $errors[] = "Sample order maximum quantity is {$sample_max_qty}"; } } else { // Normal order validation $min_quantity = intval($product_data['min_quantity'] ?? 1); if ($quantity < $min_quantity) { $errors[] = "Minimum order quantity is {$min_quantity}"; } $max_quantity = intval($product_data['maximum_order_quantity'] ?? 10000); if ($quantity > $max_quantity) { $errors[] = "Maximum order quantity is {$max_quantity}"; } } // Validate required attributes if (!empty($product_data['attributes'])) { foreach ($product_data['attributes'] as $index => $attribute) { if (isset($attribute['required']) && $attribute['required']) { $has_selection = false; if (isset($config['attributes'][$index])) { $selected = $config['attributes'][$index]; if (is_array($selected)) { $has_selection = !empty($selected); } else { $has_selection = $selected !== '' && $selected !== null; } } if (!$has_selection) { $errors[] = "Required attribute missing: {$attribute['name']}"; } } } } // Validate calculator costs if (isset($config['calculator_costs'])) { if (!is_numeric($config['calculator_costs']['area'] ?? 0)) { $errors[] = 'Invalid area calculator cost'; } if (!is_numeric($config['calculator_costs']['volume'] ?? 0)) { $errors[] = 'Invalid volume calculator cost'; } } return [ 'valid' => empty($errors), 'errors' => $errors, 'sanitized_config' => $config ]; } } // =============================== // 📁 FILE UPLOAD FUNCTIONS // =============================== /** * Process design files upload (Enhanced) * 处理设计文件上传(增强版) */ if (!function_exists('mingkos_process_design_files')) { function mingkos_process_design_files($product_id, $files) { $uploaded_files = []; if (empty($files) || !isset($files['design_files'])) { return $uploaded_files; } // Check if file upload is enabled for this product if (function_exists('mingkos_get_product_data_enhanced')) { $product_data = mingkos_get_product_data_enhanced($product_id); if (!$product_data['file_upload_enabled']) { return $uploaded_files; } $max_size = $product_data['max_file_size'] * 1024 * 1024; } else { $max_size = 100 * 1024 * 1024; // Default 100MB } $allowed_types = ['pdf', 'ai', 'psd', 'jpg', 'jpeg', 'png', 'zip', 'eps', 'cdr', 'svg']; // Process each file if (is_array($files['design_files']['name'])) { $file_count = count($files['design_files']['name']); for ($i = 0; $i < $file_count; $i++) { if ($files['design_files']['error'][$i] !== UPLOAD_ERR_OK) { continue; } $file = [ 'name' => $files['design_files']['name'][$i], 'type' => $files['design_files']['type'][$i], 'tmp_name' => $files['design_files']['tmp_name'][$i], 'error' => $files['design_files']['error'][$i], 'size' => $files['design_files']['size'][$i] ]; $file_result = mingkos_process_single_design_file($file, $max_size, $allowed_types); if ($file_result) { $uploaded_files[] = $file_result; } } } else { if ($files['design_files']['error'] === UPLOAD_ERR_OK) { $file_result = mingkos_process_single_design_file($files['design_files'], $max_size, $allowed_types); if ($file_result) { $uploaded_files[] = $file_result; } } } return $uploaded_files; } } /** * Process single design file * 处理单个设计文件 */ if (!function_exists('mingkos_process_single_design_file')) { function mingkos_process_single_design_file($file, $max_size, $allowed_types) { // Check for upload errors if ($file['error'] !== UPLOAD_ERR_OK) { mingkos_log('File upload error', [ 'file' => $file['name'], 'error' => $file['error'], ]); return false; } // Check file size if ($file['size'] > $max_size) { mingkos_log('File too large', [ 'file' => $file['name'], 'size' => $file['size'], 'max_size' => $max_size ]); return false; } // Check file type $file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($file_extension, $allowed_types)) { mingkos_log('Invalid file type', [ 'file' => $file['name'], 'type' => $file_extension, 'allowed' => $allowed_types ]); return false; } // Upload file to WordPress media library require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $upload = wp_handle_upload($file, ['test_form' => false]); if (!isset($upload['error'])) { $attachment = [ 'post_mime_type' => $file['type'], 'post_title' => preg_replace('/\.[^.]+$/', '', basename($file['name'])), 'post_content' => '', 'post_status' => 'inherit', 'guid' => $upload['url'] ]; $attachment_id = wp_insert_attachment($attachment, $upload['file']); if (!is_wp_error($attachment_id)) { $attachment_data = wp_generate_attachment_metadata($attachment_id, $upload['file']); wp_update_attachment_metadata($attachment_id, $attachment_data); mingkos_log('File uploaded successfully', [ 'file' => $file['name'], 'attachment_id' => $attachment_id, 'url' => $upload['url'] ]); return [ 'id' => $attachment_id, 'name' => $file['name'], 'url' => $upload['url'], 'size' => $file['size'], 'type' => $file['type'] ]; } } return false; } } /** * Fallback design file processing * 备用设计文件处理 */ if (!function_exists('mingkos_process_design_files_fallback')) { function mingkos_process_design_files_fallback($files) { $uploaded_files = []; if (empty($files) || !isset($files['design_files'])) { return $uploaded_files; } require_once(ABSPATH . 'wp-admin/includes/file.php'); if (is_array($files['design_files']['name'])) { foreach ($files['design_files']['name'] as $key => $value) { if ($files['design_files']['name'][$key] && $files['design_files']['error'][$key] === UPLOAD_ERR_OK) { $file = [ 'name' => $files['design_files']['name'][$key], 'type' => $files['design_files']['type'][$key], 'tmp_name' => $files['design_files']['tmp_name'][$key], 'error' => $files['design_files']['error'][$key], 'size' => $files['design_files']['size'][$key] ]; $upload = wp_handle_upload($file, ['test_form' => false]); if (!isset($upload['error'])) { $uploaded_files[] = [ 'url' => $upload['url'], 'name' => $file['name'] ]; } } } } else { if ($files['design_files']['name'] && $files['design_files']['error'] === UPLOAD_ERR_OK) { $upload = wp_handle_upload($files['design_files'], ['test_form' => false]); if (!isset($upload['error'])) { $uploaded_files[] = [ 'url' => $upload['url'], 'name' => $files['design_files']['name'] ]; } } } return $uploaded_files; } } // =============================== // 🔧 UTILITY FUNCTIONS // =============================== /** * Generate configuration hash * 生成配置哈希值 */ if (!function_exists('mingkos_generate_config_hash')) { function mingkos_generate_config_hash($config) { $hash_data = [ 'order_type' => $config['order_type'] ?? 'normal', 'quantity' => $config['quantity'] ?? 1, 'attributes' => $config['attributes'] ?? [], 'printing_options' => $config['printing_options'] ?? [], 'packaging_options' => $config['packaging_options'] ?? [], 'packaging_type' => $config['packaging_type'] ?? 'standard', 'dynamic_parameters' => $config['dynamic_parameters'] ?? [], 'calculator_costs' => $config['calculator_costs'] ?? ['area' => 0, 'volume' => 0] ]; return md5(serialize($hash_data)); } } /** * Get enhanced cart fragments * 获取增强版购物车片段 */ if (!function_exists('mingkos_get_enhanced_cart_fragments')) { function mingkos_get_enhanced_cart_fragments() { if (!class_exists('WooCommerce') || !WC()->cart) { return []; } $fragments = []; // Cart count $fragments['span.mingkos-cart-count'] = '' . WC()->cart->get_cart_contents_count() . ''; // Cart total $fragments['span.mingkos-cart-total'] = '' . WC()->cart->get_cart_total() . ''; // Mini cart widget if (function_exists('woocommerce_mini_cart')) { ob_start(); woocommerce_mini_cart(); $fragments['div.widget_shopping_cart_content'] = '
' . ob_get_clean() . '
'; } return apply_filters('mingkos_cart_fragments', $fragments); } } // =============================== // 💰 PRICE CALCULATION AJAX (UNIFIED) // =============================== /** * AJAX handler for price calculation (UNIFIED VERSION) * AJAX价格计算处理程序(统一版本) */ if (!function_exists('mingkos_ajax_calculate_price')) { add_action('wp_ajax_mingkos_calculate_price', 'mingkos_ajax_calculate_price'); add_action('wp_ajax_nopriv_mingkos_calculate_price', 'mingkos_ajax_calculate_price'); function mingkos_ajax_calculate_price() { try { // Verify nonce $nonce = sanitize_text_field($_POST['nonce'] ?? ''); if (!wp_verify_nonce($nonce, 'mingkos_price_calc')) { throw new Exception('Security check failed'); } $product_id = intval($_POST['product_id'] ?? 0); if (!$product_id) { throw new Exception('Product ID is required'); } // Get configuration $config = mingkos_sanitize_ajax_configuration($_POST); // Calculate price if (function_exists('mingkos_calculate_product_price_complete')) { $calculation = mingkos_calculate_product_price_complete($product_id, $config); } else if (function_exists('mingkos_calculate_product_price')) { $calculation = mingkos_calculate_product_price($product_id, $config); } else { // Fallback calculation $product = wc_get_product($product_id); if (!$product) { throw new Exception('Product not found'); } $base_price = $product->get_price(); $quantity = $config['quantity']; $total = $base_price * $quantity; $calculation = [ 'success' => true, 'price_per_unit' => $base_price, 'total' => $total, 'subtotal' => $total, 'base_price' => $base_price, 'currency' => get_woocommerce_currency() ]; } if (!$calculation['success']) { throw new Exception($calculation['error'] ?? 'Price calculation failed'); } // Format response $response = [ 'success' => true, 'data' => [ 'product_id' => $product_id, 'quantity' => $config['quantity'], 'order_type' => $config['order_type'], 'base_price' => $calculation['base_price'] ?? 0, 'attribute_adjustments' => $calculation['attribute_adjustments'] ?? 0, 'printing_costs' => $calculation['printing_costs'] ?? 0, 'packaging_costs' => $calculation['packaging_costs'] ?? 0, 'dynamic_param_adjustments' => $calculation['dynamic_param_adjustments'] ?? 0, 'calculator_costs' => $calculation['calculator_costs'] ?? 0, 'sample_discount' => $calculation['sample_discount'] ?? 0, 'quantity_discount' => $calculation['quantity_discount'] ?? 0, 'subtotal' => $calculation['subtotal'] ?? 0, 'total' => $calculation['total'] ?? 0, 'price_per_unit' => $calculation['price_per_unit'] ?? 0, 'currency' => get_woocommerce_currency(), 'currency_symbol' => get_woocommerce_currency_symbol(), 'breakdown' => $calculation['breakdown'] ?? [], 'formatted_total' => wc_price($calculation['total']), 'formatted_unit_price' => wc_price($calculation['price_per_unit']), 'formatted_breakdown' => [] ] ]; // Format breakdown items if (!empty($calculation['breakdown'])) { foreach ($calculation['breakdown'] as $item) { $response['data']['formatted_breakdown'][] = [ 'label' => $item['label'] ?? '', 'value' => $item['value'] ?? 0, 'formatted' => ($item['value'] < 0 ? '-' : '+') . wc_price(abs($item['value'])), 'type' => $item['type'] ?? '', 'per_unit' => $item['per_unit'] ?? false ]; } } // Prepare breakdown HTML $breakdown_html = ''; if (!empty($calculation['breakdown'])) { $breakdown_html .= '
'; foreach ($calculation['breakdown'] as $item) { $sign = $item['value'] < 0 ? '' : '+'; $breakdown_html .= sprintf( '
%s: %s%s
', esc_attr($item['type'] ?? ''), esc_html($item['label'] ?? ''), $sign, wc_price(abs($item['value'])) ); } $breakdown_html .= '
'; } $response['breakdown_html'] = $breakdown_html; wp_send_json_success($response); } catch (Exception $e) { wp_send_json_error([ 'success' => false, 'message' => $e->getMessage(), 'code' => 'PRICE_CALC_ERROR' ]); } } } // =============================== // 💾 CONFIGURATION SAVE/LOAD AJAX // =============================== /** * AJAX handler for saving configuration * AJAX配置保存处理程序 */ if (!function_exists('mingkos_ajax_save_configuration')) { add_action('wp_ajax_mingkos_save_configuration', 'mingkos_ajax_save_configuration'); add_action('wp_ajax_nopriv_mingkos_save_configuration', 'mingkos_ajax_save_configuration'); function mingkos_ajax_save_configuration() { try { // Verify nonce $nonce = sanitize_text_field($_POST['nonce'] ?? ''); if (!wp_verify_nonce($nonce, 'mingkos_save_config')) { throw new Exception('Security check failed'); } $product_id = intval($_POST['product_id'] ?? 0); $config_name = sanitize_text_field($_POST['config_name'] ?? ''); if (!$product_id || empty($config_name)) { throw new Exception('Product ID and configuration name are required'); } // Get configuration data $config_data = mingkos_sanitize_ajax_configuration($_POST); // Prepare configuration for saving $configuration = [ 'product_id' => $product_id, 'timestamp' => current_time('timestamp'), 'name' => $config_name, 'data' => $config_data, 'hash' => mingkos_generate_config_hash($config_data) ]; // Save to user meta (if logged in) or session if (is_user_logged_in()) { $user_id = get_current_user_id(); $saved_configs = get_user_meta($user_id, 'mingkos_saved_configurations', true); if (empty($saved_configs) || !is_array($saved_configs)) { $saved_configs = []; } if (!isset($saved_configs[$product_id]) || !is_array($saved_configs[$product_id])) { $saved_configs[$product_id] = []; } $saved_configs[$product_id][] = $configuration; update_user_meta($user_id, 'mingkos_saved_configurations', $saved_configs); $config_id = count($saved_configs[$product_id]) - 1; } else { // Save to session if (WC()->session) { $saved_configs = WC()->session->get('mingkos_saved_configurations', []); if (empty($saved_configs[$product_id]) || !is_array($saved_configs[$product_id])) { $saved_configs[$product_id] = []; } $saved_configs[$product_id][] = $configuration; WC()->session->set('mingkos_saved_configurations', $saved_configs); $config_id = count($saved_configs[$product_id]) - 1; } else { // Fallback to localStorage (handled by frontend) $config_id = time(); } } wp_send_json_success([ 'success' => true, 'message' => __('Configuration saved successfully', 'mingkos'), 'config_id' => $config_id, 'config_hash' => $configuration['hash'], 'timestamp' => $configuration['timestamp'], 'config_name' => $config_name ]); } catch (Exception $e) { wp_send_json_error([ 'success' => false, 'message' => $e->getMessage(), 'code' => 'SAVE_CONFIG_ERROR' ]); } } } /** * AJAX handler for loading saved configurations * AJAX加载保存的配置处理程序 */ if (!function_exists('mingkos_ajax_load_configuration')) { add_action('wp_ajax_mingkos_load_configuration', 'mingkos_ajax_load_configuration'); add_action('wp_ajax_nopriv_mingkos_load_configuration', 'mingkos_ajax_load_configuration'); function mingkos_ajax_load_configuration() { try { // Verify nonce $nonce = sanitize_text_field($_POST['nonce'] ?? ''); if (!wp_verify_nonce($nonce, 'mingkos_load_configs')) { throw new Exception('Security check failed'); } $product_id = intval($_POST['product_id'] ?? 0); if (!$product_id) { throw new Exception('Product ID is required'); } $configurations = []; // Get from user meta or session if (is_user_logged_in()) { $user_id = get_current_user_id(); $saved_configs = get_user_meta($user_id, 'mingkos_saved_configurations', true); if (!empty($saved_configs[$product_id]) && is_array($saved_configs[$product_id])) { $configurations = $saved_configs[$product_id]; } } else if (WC()->session) { $saved_configs = WC()->session->get('mingkos_saved_configurations', []); if (!empty($saved_configs[$product_id]) && is_array($saved_configs[$product_id])) { $configurations = $saved_configs[$product_id]; } } wp_send_json_success([ 'success' => true, 'configurations' => $configurations, 'count' => count($configurations) ]); } catch (Exception $e) { wp_send_json_error([ 'success' => false, 'message' => $e->getMessage(), 'code' => 'LOAD_CONFIGS_ERROR' ]); } } } /** * AJAX handler for deleting saved configuration * 处理删除保存配置的AJAX请求 */ if (!function_exists('mingkos_ajax_delete_configuration')) { add_action('wp_ajax_mingkos_delete_configuration', 'mingkos_ajax_delete_configuration'); function mingkos_ajax_delete_configuration() { try { // Check nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mingkos_config')) { throw new Exception('Security check failed.'); } // Check if user is logged in if (!is_user_logged_in()) { throw new Exception('You must be logged in to delete configurations.'); } $user_id = get_current_user_id(); $config_id = isset($_POST['config_id']) ? intval($_POST['config_id']) : -1; $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0; if ($config_id < 0 || !$product_id) { throw new Exception('Invalid configuration ID or product ID.'); } $saved_configs = get_user_meta($user_id, 'mingkos_saved_configurations', true); if (!empty($saved_configs[$product_id]) && is_array($saved_configs[$product_id])) { if (isset($saved_configs[$product_id][$config_id])) { unset($saved_configs[$product_id][$config_id]); $saved_configs[$product_id] = array_values($saved_configs[$product_id]); update_user_meta($user_id, 'mingkos_saved_configurations', $saved_configs); wp_send_json_success([ 'success' => true, 'message' => 'Configuration deleted successfully!', 'config_id' => $config_id ]); } else { throw new Exception('Configuration not found.'); } } else { throw new Exception('No configurations found for this product.'); } } catch (Exception $e) { wp_send_json_error([ 'message' => $e->getMessage() ]); } wp_die(); } } // =============================== // 🧮 CALCULATOR AJAX // =============================== /** * AJAX handler for calculator calculations * AJAX计算器计算处理程序 */ if (!function_exists('mingkos_ajax_calculator_calculation')) { add_action('wp_ajax_mingkos_calculator_calculation', 'mingkos_ajax_calculator_calculation'); add_action('wp_ajax_nopriv_mingkos_calculator_calculation', 'mingkos_ajax_calculator_calculation'); function mingkos_ajax_calculator_calculation() { try { // Verify nonce $nonce = sanitize_text_field($_POST['nonce'] ?? ''); if (!wp_verify_nonce($nonce, 'mingkos_calculator')) { throw new Exception('Security check failed'); } $product_id = intval($_POST['product_id'] ?? 0); $calculator_type = sanitize_text_field($_POST['calculator_type'] ?? ''); if (!$product_id || empty($calculator_type)) { throw new Exception('Product ID and calculator type are required'); } $response = []; switch ($calculator_type) { case 'area': $length = floatval($_POST['length'] ?? 0); $width = floatval($_POST['width'] ?? 0); $length_unit = sanitize_text_field($_POST['length_unit'] ?? 'm'); $quantity = intval($_POST['quantity'] ?? 1); if (function_exists('mingkos_calculate_area_price_updated')) { $result = mingkos_calculate_area_price_updated( $product_id, $length, $width, $length_unit, $quantity ); $response = [ 'calculator_type' => 'area', 'area_sqm' => $result['area_sqm'] ?? 0, 'area_sqft' => $result['area_sqft'] ?? 0, 'price_per_sqm' => $result['price_per_sqm'] ?? 0, 'price_per_sqft' => $result['price_per_sqft'] ?? 0, 'total_price' => $result['total_price'] ?? 0, 'formatted_total' => wc_price($result['total_price'] ?? 0), 'formatted_area' => number_format($result['area_sqm'] ?? 0, 2) . ' m² (' . number_format($result['area_sqft'] ?? 0, 2) . ' ft²)' ]; } break; case 'volume': $length = floatval($_POST['length'] ?? 0); $width = floatval($_POST['width'] ?? 0); $height = floatval($_POST['height'] ?? 0); $unit = sanitize_text_field($_POST['unit'] ?? 'm'); $quantity = intval($_POST['quantity'] ?? 1); if (function_exists('mingkos_calculate_volume_price_updated')) { $result = mingkos_calculate_volume_price_updated( $product_id, $length, $width, $height, $unit, $quantity ); $response = [ 'calculator_type' => 'volume', 'volume_cubic' => $result['volume_cubic'] ?? 0, 'volume_cubic_inch' => $result['volume_cubic_inch'] ?? 0, 'price_per_cubic' => $result['price_per_cubic'] ?? 0, 'price_per_cubic_inch' => $result['price_per_cubic_inch'] ?? 0, 'total_price' => $result['total_price'] ?? 0, 'formatted_total' => wc_price($result['total_price'] ?? 0), 'formatted_volume' => number_format($result['volume_cubic'] ?? 0, 4) . ' m³ (' . number_format($result['volume_cubic_inch'] ?? 0, 2) . ' in³)' ]; } break; default: throw new Exception('Invalid calculator type'); } wp_send_json_success($response); } catch (Exception $e) { wp_send_json_error([ 'success' => false, 'message' => $e->getMessage(), 'code' => 'CALCULATOR_ERROR' ]); } } } // =============================== // 📎 FILE UPLOAD AJAX // =============================== /** * AJAX handler for temporary file upload * 处理临时文件上传的AJAX请求 */ if (!function_exists('mingkos_ajax_upload_file')) { add_action('wp_ajax_mingkos_upload_file', 'mingkos_ajax_upload_file'); add_action('wp_ajax_nopriv_mingkos_upload_file', 'mingkos_ajax_upload_file'); function mingkos_ajax_upload_file() { try { // Check nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mingkos_file_upload')) { throw new Exception('Security check failed.'); } // Check file upload if (empty($_FILES['file'])) { throw new Exception('No file uploaded.'); } $file = $_FILES['file']; // Check for upload errors if ($file['error'] !== UPLOAD_ERR_OK) { $upload_errors = [ UPLOAD_ERR_INI_SIZE => 'File exceeds upload limit.', UPLOAD_ERR_FORM_SIZE => 'File exceeds form limit.', UPLOAD_ERR_PARTIAL => 'File was only partially uploaded.', UPLOAD_ERR_NO_FILE => 'No file was uploaded.', UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder.', UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.', UPLOAD_ERR_EXTENSION => 'File upload stopped by extension.' ]; $error_msg = isset($upload_errors[$file['error']]) ? $upload_errors[$file['error']] : 'Unknown upload error.'; throw new Exception('File upload error: ' . $error_msg); } // Check file size (max 100MB) $max_size = 100 * 1024 * 1024; if ($file['size'] > $max_size) { throw new Exception('File size exceeds maximum limit of 100MB.'); } // Check file type $allowed_types = ['pdf', 'ai', 'psd', 'jpg', 'jpeg', 'png', 'zip', 'eps', 'cdr', 'svg']; $file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($file_ext, $allowed_types)) { throw new Exception('File type not accepted. Allowed types: ' . implode(', ', $allowed_types)); } // Upload file require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $upload_overrides = ['test_form' => false]; $upload = wp_handle_upload($file, $upload_overrides); if (isset($upload['error'])) { throw new Exception('Upload error: ' . $upload['error']); } // Create attachment post $filetype = wp_check_filetype(basename($upload['file']), null); $attachment = [ 'post_mime_type' => $filetype['type'], 'post_title' => preg_replace('/\.[^.]+$/', '', basename($upload['file'])), 'post_content' => '', 'post_status' => 'inherit' ]; $attach_id = wp_insert_attachment($attachment, $upload['file']); // Generate attachment metadata $attach_data = wp_generate_attachment_metadata($attach_id, $upload['file']); wp_update_attachment_metadata($attach_id, $attach_data); wp_send_json_success([ 'success' => true, 'file_id' => $attach_id, 'file_url' => $upload['url'], 'file_name' => basename($upload['url']), 'file_size' => $file['size'], 'message' => 'File uploaded successfully.' ]); } catch (Exception $e) { wp_send_json_error([ 'message' => $e->getMessage() ]); } wp_die(); } } // =============================== // 🔧 COMPATIBILITY FUNCTIONS // =============================== /** * Compatibility function for old AJAX calls * 旧版AJAX调用的兼容函数 */ if (!function_exists('mingkos_ajax_add_to_cart')) { add_action('wp_ajax_mingkos_add_to_cart', 'mingkos_ajax_add_to_cart'); add_action('wp_ajax_nopriv_mingkos_add_to_cart', 'mingkos_ajax_add_to_cart'); function mingkos_ajax_add_to_cart() { // Forward to enhanced handler for compatibility if (function_exists('mingkos_enhanced_add_to_cart_ajax')) { mingkos_enhanced_add_to_cart_ajax(); } else { wp_send_json_error(['message' => 'Add to cart handler not available']); } } } // =============================== // 🎯 INITIALIZATION // =============================== /** * Initialize enhanced AJAX handlers * 初始化增强版AJAX处理程序 */ if (!function_exists('mingkos_init_enhanced_ajax')) { add_action('init', 'mingkos_init_enhanced_ajax'); function mingkos_init_enhanced_ajax() { // Check if WooCommerce is active if (!class_exists('WooCommerce')) { add_action('admin_notices', function() { echo '

mingkos requires WooCommerce to be installed and activated.

'; }); return; } // Log AJAX initialization if (defined('WP_DEBUG') && WP_DEBUG) { error_log('mingkos Enhanced AJAX handlers initialized (v5.3.1)'); } } } // End of file: mingkos-ajax-enhanced-merged.php/** * mingkos Customization System - WooCommerce Order Integration (Final + Secure Download) * * @version 8.2.1 - 添加函数存在性检查和错误处理 */ if (!defined('ABSPATH')) { exit; } // =============================== // 🔐 安全定义设计目录常量 // =============================== if (!defined('mingkos_DESIGNS_DIR')) { $upload_dir = wp_upload_dir(); define('mingkos_DESIGNS_DIR', WP_CONTENT_DIR . '/customer_designs/'); define('mingkos_DESIGNS_URL', content_url() . '/customer_designs/'); } // 确保目录存在 add_action('init', 'mingkos_ensure_designs_directory'); function mingkos_ensure_designs_directory() { if (!file_exists(mingkos_DESIGNS_DIR)) { wp_mkdir_p(mingkos_DESIGNS_DIR); // 添加 .htaccess 保护 $htaccess = mingkos_DESIGNS_DIR . '.htaccess'; if (!file_exists($htaccess)) { file_put_contents($htaccess, "Deny from all\n"); } // 添加 index.html 防止目录浏览 file_put_contents(mingkos_DESIGNS_DIR . 'index.html', ''); } } // =============================== // 📦 将购物车项中的定制数据复制到订单项 // =============================== add_action('woocommerce_checkout_create_order_line_item', 'mingkos_order_item_meta_handler', 10, 4); if (!function_exists('mingkos_order_item_meta_handler')) { function mingkos_order_item_meta_handler($item, $cart_item_key, $values, $order) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log('=== [Checkout] mingkos_order_item_meta_handler ==='); error_log('Cart Item Key: ' . $cart_item_key); } // 保存配置 if (isset($values['mingkos_config']) && !empty($values['mingkos_config'])) { $item->add_meta_data('_mingkos_config', $values['mingkos_config']); } // 保存设计文件 if (isset($values['mingkos_design_files'])) { $item->add_meta_data('_mingkos_design_files', $values['mingkos_design_files']); } // 保存价格数据 if (isset($values['mingkos_price_data'])) { $item->add_meta_data('_mingkos_price_data', $values['mingkos_price_data']); } // 标记为定制产品 if (isset($values['mingkos_customization'])) { $item->add_meta_data('_mingkos_customized', 'yes'); } // 保存用户ID(用于文件关联) $user_id = get_current_user_id(); if ($user_id) { $item->add_meta_data('_mingkos_user_id', $user_id); } // 生成并保存设计ID(用于后续追溯) $design_id = uniqid('design_'); $item->add_meta_data('_mingkos_design_id', $design_id); } } // =============================== // 📦 订单创建后:将临时文件移动到订单号目录 // =============================== add_action('woocommerce_checkout_update_order_meta', 'mingkos_move_temp_files_to_order_dir', 10, 2); if (!function_exists('mingkos_move_temp_files_to_order_dir')) { function mingkos_move_temp_files_to_order_dir($order_id, $data) { // 确保目录存在 mingkos_ensure_designs_directory(); if (defined('WP_DEBUG') && WP_DEBUG) { error_log("=== mingkos_move_temp_files_to_order_dir 开始,订单ID: $order_id ==="); } $order = wc_get_order($order_id); if (!$order) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log('订单对象获取失败'); } return; } // 检查是否有权限访问订单 if (!current_user_can('manage_woocommerce') && $order->get_user_id() !== get_current_user_id()) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log('无权限访问此订单'); } return; } $target_base = mingkos_DESIGNS_DIR . $order_id . '/'; if (!file_exists($target_base)) { if (!wp_mkdir_p($target_base)) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log("无法创建目录: $target_base"); } return; } file_put_contents($target_base . 'index.html', ''); if (defined('WP_DEBUG') && WP_DEBUG) { error_log("目录创建成功: $target_base"); } } // 遍历订单项 foreach ($order->get_items() as $item_id => $item) { $design_files = $item->get_meta('_mingkos_design_files'); // 安全处理:如果是字符串则解码 if (is_string($design_files)) { $design_files = json_decode($design_files, true); } if (empty($design_files) || !is_array($design_files)) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log("订单项 $item_id 无设计文件"); } continue; } if (defined('WP_DEBUG') && WP_DEBUG) { error_log("订单项 $item_id 设计文件原始数据: " . print_r($design_files, true)); } $updated_files = []; $product_id = $item->get_product_id(); $user_id = $item->get_meta('_mingkos_user_id'); if (!$user_id) { $user_id = $order->get_user_id(); } foreach ($design_files as $index => $file) { // 安全检查:确保 $file 是数组 if (!is_array($file)) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log("文件 $index 不是数组格式,跳过"); } $updated_files[] = $file; continue; } $old_path = ''; // 尝试从不同字段获取源路径 if (isset($file['filepath']) && file_exists($file['filepath'])) { $old_path = $file['filepath']; } elseif (isset($file['filename']) && $user_id && $product_id) { $old_path = mingkos_DESIGNS_DIR . 'temp/' . $user_id . '/' . $product_id . '/' . $file['filename']; if (!file_exists($old_path)) { $old_path = ''; } } elseif (isset($file['id'])) { // 如果是媒体库附件,不移动,保留原路径 $attachment_path = get_attached_file($file['id']); if ($attachment_path && file_exists($attachment_path)) { $updated_files[] = $file; continue; } } if (empty($old_path)) { if (defined('WP_DEBUG') && WP_DEBUG) { error_log("文件 $index 源路径不存在,保留原记录"); } $updated_files[] = $file; continue; } // 安全地生成新文件名 $new_filename = sanitize_file_name(basename($old_path)); $new_path = $target_base . $new_filename; // 如果目标文件已存在,添加后缀 $counter = 1; $original_path = $new_path; while (file_exists($new_path)) { $ext = pathinfo($original_path, PATHINFO_EXTENSION); $name = pathinfo($original_path, PATHINFO_FILENAME); $new_filename = $name . '_' . $counter . '.' . $ext; $new_path = $target_base . $new_filename; $counter++; } if (rename($old_path, $new_path)) { $file['filepath'] = $new_path; $file['filename'] = $new_filename; $file['url'] = mingkos_DESIGNS_URL . $order_id . '/' . $new_filename; $updated_files[] = $file; if (defined('WP_DEBUG') && WP_DEBUG) { error_log("文件移动成功: $old_path -> $new_path"); } } else { if (defined('WP_DEBUG') && WP_DEBUG) { error_log("文件移动失败: $old_path -> $new_path"); } $updated_files[] = $file; } } // 更新订单项元数据 if (!empty($updated_files)) { $item->update_meta_data('_mingkos_design_files', $updated_files); } // 同时更新 _mingkos_config 中的 design_files 字段(如果存在) $config = $item->get_meta('_mingkos_config'); if (is_string($config)) { $config = json_decode($config, true); } if (!empty($config) && is_array($config) && isset($config['design_files'])) { $config['design_files'] = $updated_files; $item->update_meta_data('_mingkos_config', $config); } $item->save_meta_data(); } // 清理临时目录 mingkos_cleanup_temp_directory(); if (defined('WP_DEBUG') && WP_DEBUG) { error_log("=== mingkos_move_temp_files_to_order_dir 结束 ==="); } } } /** * 清理临时目录(超过7天的文件) */ function mingkos_cleanup_temp_directory() { $temp_dir = mingkos_DESIGNS_DIR . 'temp/'; if (!file_exists($temp_dir)) { return; } $now = time(); $expire_time = 7 * 24 * 60 * 60; // 7天 $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($temp_dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($files as $file) { if ($file->isFile() && $file->getFilename() !== 'index.html') { $filetime = $file->getMTime(); if ($now - $filetime > $expire_time) { @unlink($file->getRealPath()); if (defined('WP_DEBUG') && WP_DEBUG) { error_log("清理过期临时文件: " . $file->getFilename()); } } } } } // =============================== // 🔍 获取产品数据(带安全检查) // =============================== if (!function_exists('mingkos_get_order_product_data_safe')) { function mingkos_get_order_product_data_safe($product_id) { // 尝试多种函数名 if (function_exists('mingkos_get_product_data_enhanced')) { return mingkos_get_product_data_enhanced($product_id); } elseif (function_exists('mingkos_get_product_data')) { return mingkos_get_product_data($product_id); } elseif (function_exists('mingkos_get_product_data_v4')) { return mingkos_get_product_data_v4($product_id); } // 后备:从产品获取基本信息 $product = wc_get_product($product_id); if (!$product) { return null; } return [ 'product_id' => $product_id, 'product_name' => $product->get_name(), 'attributes' => [], 'printing_options' => [], 'packaging_options' => [], 'base_price' => $product->get_price(), ]; } } // =============================== // 🔍 安全解码元数据 // =============================== if (!function_exists('mingkos_safe_decode_meta')) { function mingkos_safe_decode_meta($meta_value) { if (empty($meta_value)) { return []; } if (is_array($meta_value)) { return $meta_value; } if (is_string($meta_value)) { $decoded = json_decode($meta_value, true); if (is_array($decoded)) { return $decoded; } } return []; } } // =============================== // 🔍 在后台订单详情页面显示定制信息 // =============================== add_action('woocommerce_before_order_itemmeta', 'mingkos_display_order_item_customization_table', 10, 3); if (!function_exists('mingkos_display_order_item_customization_table')) { function mingkos_display_order_item_customization_table($item_id, $item, $product) { // 获取并解码配置 $config = $item->get_meta('_mingkos_config'); $config = mingkos_safe_decode_meta($config); if (empty($config)) { return; } // 获取并解码价格数据 $price_data = $item->get_meta('_mingkos_price_data'); $price_data = mingkos_safe_decode_meta($price_data); // 获取并解码设计文件 $design_files = $item->get_meta('_mingkos_design_files'); $design_files = mingkos_safe_decode_meta($design_files); $product_id = $item->get_product_id(); $order = $item->get_order(); // 安全获取产品数据 $product_data = mingkos_get_order_product_data_safe($product_id); // 基础信息 $quantity = isset($config['quantity']) ? intval($config['quantity']) : $item->get_quantity(); $unit_price = 0; $total_price = 0; $base_price = 0; if (!empty($price_data)) { $unit_price = isset($price_data['price_per_unit']) ? floatval($price_data['price_per_unit']) : 0; $total_price = isset($price_data['total']) ? floatval($price_data['total']) : 0; $base_price = isset($price_data['base_price']) ? floatval($price_data['base_price']) : 0; } else { $unit_price = $item->get_subtotal() / max(1, $item->get_quantity()); $total_price = $item->get_subtotal(); $base_price = $unit_price; } $order_type = isset($config['order_type']) ? $config['order_type'] : 'normal'; $order_type_label = ($order_type === 'sample') ? __('拿样订单', 'mingkos') : __('正常订单', 'mingkos'); // ========== 主表格:定制详细信息 ========== echo '
'; echo '' . __('🎨 定制详细信息', 'mingkos') . ''; echo ''; echo ''; // 价格行 echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; // ---- 产品属性 ---- echo ''; echo ''; echo ''; echo ''; // ---- 印刷工艺 ---- echo ''; echo ''; echo ''; echo ''; // ---- 包装选项 ---- echo ''; echo ''; echo ''; echo ''; // ---- 包装类型 ---- if (isset($config['packaging_type'])) { $packaging_type = $config['packaging_type']; $packaging_type_label = ($packaging_type === 'custom') ? __('定制包装', 'mingkos') : __('标准包装', 'mingkos'); echo ''; echo ''; echo ''; echo ''; } // ---- 动态参数 ---- if (!empty($config['dynamic_parameters']) && is_array($config['dynamic_parameters'])) { echo ''; echo ''; echo ''; echo ''; } // ---- 计算器成本 ---- $has_calculator = false; $calculator_html = ''; if (!empty($config['calculator_costs']) && is_array($config['calculator_costs'])) { $costs = $config['calculator_costs']; if (!empty($costs['area'])) { $calculator_html .= __('📐 面积成本', 'mingkos') . ': ' . wc_price($costs['area']) . '
'; $has_calculator = true; } if (!empty($costs['volume'])) { $calculator_html .= __('📦 体积成本', 'mingkos') . ': ' . wc_price($costs['volume']) . '
'; $has_calculator = true; } } echo ''; echo ''; echo ''; echo ''; // ---- 计算器输入值 ---- if (!empty($config['calculator_inputs']) && is_array($config['calculator_inputs'])) { $inputs = $config['calculator_inputs']; echo ''; echo ''; echo ''; echo ''; } // ---- 订单备注 ---- if (!empty($config['order_note'])) { echo ''; echo ''; echo ''; echo ''; } // ---- 设计文件(安全下载链接) ---- echo ''; echo ''; echo ''; echo ''; echo ''; echo '
' . __('💰 单价', 'mingkos') . '' . wc_price($unit_price) . '
' . __('📦 总价', 'mingkos') . '' . wc_price($total_price) . '
' . __('🔢 数量', 'mingkos') . '' . $quantity . __(' 件', 'mingkos') . '
' . __('📋 订单类型', 'mingkos') . '' . esc_html($order_type_label) . '
' . __('💵 基础价格', 'mingkos') . '' . wc_price($base_price) . __(' /件', 'mingkos') . '
' . __('🏷️ 产品属性', 'mingkos') . ''; if (!empty($config['attributes']) && is_array($config['attributes'])) { $attributes = $config['attributes']; $has_display = false; // 检查是索引数组还是关联数组 $is_indexed = array_keys($attributes) === range(0, count($attributes)-1); if ($is_indexed) { foreach ($attributes as $attr_index => $selected_value) { $attr_name = '属性 #' . ($attr_index + 1); $display_value = ''; if ($product_data && isset($product_data['attributes'][$attr_index])) { $attr_data = $product_data['attributes'][$attr_index]; $attr_name = isset($attr_data['name']) ? $attr_data['name'] : $attr_name; if (is_array($selected_value)) { $labels = []; foreach ($selected_value as $val) { if (is_numeric($val) && isset($attr_data['options'][$val])) { $labels[] = $attr_data['options'][$val]['label'] ?? $val; } else { $labels[] = $val; } } $display_value = implode(', ', $labels); } else { if (is_numeric($selected_value) && isset($attr_data['options'][$selected_value])) { $display_value = $attr_data['options'][$selected_value]['label'] ?? $selected_value; } else { $display_value = $selected_value; } } } else { $display_value = is_array($selected_value) ? implode(', ', $selected_value) : $selected_value; } if (!empty($display_value)) { echo '
' . esc_html($attr_name) . ': ' . esc_html($display_value) . '
'; $has_display = true; } } } else { foreach ($attributes as $key => $value) { $display = is_array($value) ? implode(', ', $value) : $value; echo '
' . esc_html($key) . ': ' . esc_html($display) . '
'; $has_display = true; } } if (!$has_display) { echo '' . __('无属性选择', 'mingkos') . ''; } } else { echo '' . __('无属性选择', 'mingkos') . ''; } echo '
' . __('🖨️ 印刷工艺', 'mingkos') . ''; if (!empty($config['printing_options']) && is_array($config['printing_options'])) { $printing_labels = []; foreach ($config['printing_options'] as $value) { $found = false; if ($product_data && isset($product_data['printing_options']) && is_array($product_data['printing_options'])) { foreach ($product_data['printing_options'] as $po) { if (isset($po['key']) && $po['key'] == $value) { $printing_labels[] = isset($po['label']) ? $po['label'] : $value; $found = true; break; } } } if (!$found) { $printing_labels[] = $value; } } echo implode('
', array_map('esc_html', $printing_labels)); } else { echo '' . __('未选择印刷工艺', 'mingkos') . ''; } echo '
' . __('📦 包装选项', 'mingkos') . ''; if (!empty($config['packaging_options']) && is_array($config['packaging_options'])) { $packaging_labels = []; foreach ($config['packaging_options'] as $value) { $found = false; if ($product_data && isset($product_data['packaging_options']) && is_array($product_data['packaging_options'])) { foreach ($product_data['packaging_options'] as $po) { if (isset($po['key']) && $po['key'] == $value) { $packaging_labels[] = isset($po['label']) ? $po['label'] : $value; $found = true; break; } } } if (!$found) { $packaging_labels[] = $value; } } echo implode('
', array_map('esc_html', $packaging_labels)); } else { echo '' . __('未选择包装选项', 'mingkos') . ''; } echo '
' . __('📦 包装类型', 'mingkos') . '' . esc_html($packaging_type_label) . '
' . __('⚙️ 动态参数', 'mingkos') . ''; foreach ($config['dynamic_parameters'] as $key => $value) { $display_value = is_array($value) ? implode(', ', $value) : $value; echo '
' . esc_html($key) . ': ' . esc_html($display_value) . '
'; } echo '
' . __('🧮 计算器成本', 'mingkos') . ''; if ($has_calculator) { echo $calculator_html; } else { echo '' . __('未使用计算器', 'mingkos') . ''; } echo '
' . __('📏 计算器输入', 'mingkos') . ''; if (!empty($inputs['area']) && is_array($inputs['area'])) { $a = $inputs['area']; echo __('面积', 'mingkos') . ': ' . esc_html($a['length'] ?? 0) . ' ' . esc_html($a['length_unit'] ?? 'm') . ' × ' . esc_html($a['width'] ?? 0) . ' ' . esc_html($a['width_unit'] ?? 'm') . '
'; } if (!empty($inputs['volume']) && is_array($inputs['volume'])) { $v = $inputs['volume']; echo __('体积', 'mingkos') . ': ' . esc_html($v['length'] ?? 0) . ' × ' . esc_html($v['width'] ?? 0) . ' × ' . esc_html($v['height'] ?? 0) . ' ' . esc_html($v['unit'] ?? 'm') . '
'; } echo '
' . __('📝 订单备注', 'mingkos') . '' . nl2br(esc_html($config['order_note'])) . '
' . __('📎 设计文件', 'mingkos') . ''; if (!empty($design_files) && is_array($design_files) && $order) { $order_id = $order->get_id(); foreach ($design_files as $index => $file) { if (!is_array($file)) { continue; } $file_name = isset($file['name']) ? $file['name'] : (isset($file['original_name']) ? $file['original_name'] : '文件_' . $index); $download_url = admin_url('admin-ajax.php?action=mingkos_download_file&order_id=' . $order_id . '&file_id=' . $index); echo ''; } } else { echo '' . __('未上传设计文件', 'mingkos') . ''; } echo '
'; echo '
'; // ========== 独立的价格明细表格 ========== if (!empty($price_data['breakdown']) && is_array($price_data['breakdown'])) { echo '
'; echo '' . __('💰 价格明细', 'mingkos') . ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ($price_data['breakdown'] as $item) { if (!is_array($item)) { continue; } $label = isset($item['label']) ? $item['label'] : __('未知项', 'mingkos'); $value = isset($item['value']) ? floatval($item['value']) : 0; $formatted = wc_price(abs($value)); $sign = $value < 0 ? '−' : '+'; $color = $value < 0 ? '#dc3545' : '#28a745'; echo ''; echo ''; echo ''; echo ''; } // 显示总计 if (!empty($price_data['total'])) { echo ''; echo ''; echo ''; echo ''; } echo ''; echo '
' . __('费用项', 'mingkos') . '' . __('金额', 'mingkos') . '
' . esc_html($label) . '' . $sign . ' ' . $formatted . '
' . __('合计', 'mingkos') . '' . wc_price($price_data['total']) . '
'; echo '
'; } } } // =============================== // 📥 安全文件下载接口 // =============================== add_action('wp_ajax_mingkos_download_file', 'mingkos_order_download_file_handler'); add_action('wp_ajax_nopriv_mingkos_download_file', 'mingkos_order_download_file_handler'); if (!function_exists('mingkos_order_download_file_handler')) { function mingkos_order_download_file_handler() { // 验证参数 $order_id = isset($_GET['order_id']) ? intval($_GET['order_id']) : 0; $file_id = isset($_GET['file_id']) ? intval($_GET['file_id']) : -1; $design_id = isset($_GET['design_id']) ? sanitize_text_field($_GET['design_id']) : ''; if ($order_id <= 0 || $file_id < 0) { wp_die('无效的下载请求', '错误', ['response' => 400]); } // 获取订单 $order = wc_get_order($order_id); if (!$order) { wp_die('订单不存在', '错误', ['response' => 404]); } // 验证权限 $user_id = get_current_user_id(); $can_download = false; if (current_user_can('manage_woocommerce')) { $can_download = true; // 管理员可以下载 } elseif ($order->get_user_id() == $user_id) { $can_download = true; // 订单所有者可以下载 } elseif (function_exists('wp_get_current_user') && wp_get_current_user()->exists()) { // 检查是否有 guest 访问权限(通过订单密钥) $order_key = isset($_GET['order_key']) ? sanitize_text_field($_GET['order_key']) : ''; if ($order_key && $order->get_order_key() === $order_key) { $can_download = true; } } if (!$can_download) { wp_die('无权下载此文件', '错误', ['response' => 403]); } // 查找文件 $file_info = null; foreach ($order->get_items() as $item) { $design_files = $item->get_meta('_mingkos_design_files'); $design_files = mingkos_safe_decode_meta($design_files); if (is_array($design_files) && isset($design_files[$file_id])) { $file_info = $design_files[$file_id]; break; } } if (!$file_info) { wp_die('文件信息不存在', '错误', ['response' => 404]); } // 获取文件路径 $file_path = ''; if (isset($file_info['filepath']) && file_exists($file_info['filepath'])) { $file_path = $file_info['filepath']; } elseif (isset($file_info['id'])) { $file_path = get_attached_file($file_info['id']); } elseif (isset($file_info['url'])) { // 尝试从URL解析路径 $upload_dir = wp_upload_dir(); $relative_path = str_replace($upload_dir['baseurl'], '', $file_info['url']); $file_path = $upload_dir['basedir'] . $relative_path; } if (!$file_path || !file_exists($file_path)) { wp_die('文件不存在', '错误', ['response' => 404]); } // 安全地提供下载 $filename = isset($file_info['name']) ? $file_info['name'] : basename($file_path); $filename = sanitize_file_name($filename); header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($file_path)); // 清除输出缓冲区 while (ob_get_level()) { ob_end_clean(); } readfile($file_path); exit; } }